// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: donggu

#pragma once

#include <map>
#include <memory>
#include <optional>
#include <string>
#include <unordered_map>

#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/base/order/subsystem_manager.h"
#include "coin2/strategy/order_executor/executor.h"
#include "coin2/strategy/system.pb.h"

namespace coin2::strategy {

namespace impl {

using coin2::exchange::base::executor::ArchiveSyncExecutor;
using coin2::exchange::base::executor::LiveAsyncExecutor;

class StrategySystemManager {
 public:
  typedef typename
      std::map<std::string, std::unique_ptr<order_executor::OrderExecutorSystem>> OeMap;
  explicit StrategySystemManager(
      const coin2::exchange::di::ConstantInjection& ci,
      const StrategyManagerConfig& config,
      int64_t timestamp_override)
      : config_(config) {
    if (timestamp_override == 0) {
      timestamp_override = GetCurrentTimestamp();
    }
    for (const auto& [name, exec_config] : config.executors()) {
      std::string mea_name = exec_config.has_mea() ? exec_config.mea() : name;
      auto mea = coin2::exchange::base::market::MarketExchangeApi::FromString(mea_name);
      std::optional<int64_t> account_id;
      for (const auto& [_, prod_config] : exec_config.products()) {
        if (account_id) {
          CHECK_EQ(*account_id, prod_config.sim_account_id());
        } else {
          account_id = prod_config.sim_account_id();
        }
      }
      acct_executors_[account_id ? *account_id : 0L][name] =
          std::make_unique<order_executor::OrderExecutorSystem>(
              name,
              mea,
              ci,
              exec_config,
              timestamp_override,
              nullptr);
    }
  }

  void Init(::coin2::exchange::base::order::OrderSystemManager* order_mgr) {
    for (auto& [_, executors] : acct_executors_) {
      (void)_;
      for (auto& [name, oe] : executors) {
        (void)name;
        // try 1. specified order_system
        if (oe->config().has_order_system()) {
          oe->set_order_system(order_mgr->GetUniqueSystemByName(oe->config().order_system()));
          continue;
        } else {
          // try 2. mea or throw
          oe->set_order_system(order_mgr->GetUniqueSystem(oe->mea()));
        }
      }
    }
  }

  const StrategyManagerConfig& config() { return config_; }

  const OeMap& order_executors(int64_t account_id) const {
    return acct_executors_.at(account_id);
  }

 private:
  const StrategyManagerConfig config_;
  std::unordered_map<int64_t, OeMap> acct_executors_;
};
}  // namespace impl
using impl::StrategySystemManager;
}  // namespace coin2::strategy
